﻿#include "pch.h"
#if _QG_ENABLE_WIDGET
#include "qgp/qgach.h"
#include "qgp/qgaud.h"
#include "qgp/qguwgs.h"

//////////////////////////////////////////////////////////////////////////
// 한줄 텍스트
KIMPL_SUBCLASS(qwTextLine, "WidgetTextLine");

//
qwTextLine::qwTextLine(qgWidgetType type /*= QGW_TEXTLINE*/)
	: super_type(type)
	, _insmode(true)
	, _carpos(0)
	, _vispos(0)
	, _selpos(0)
	, _border(3, 3)
{
	_insts.fill = true;
	_insts.border = true;
	_insts.input = true;

	_caret.visible = true;
#if _SB_WINDOWS_
	_caret.blink = (float)GetCaretBlinkTime() / 1000.0f;
#else
	_caret.blink = 0.2f;
#endif
}

//
qwTextLine::~qwTextLine()
{
}

//
void qwTextLine::OnInitialize()
{
	super_type::OnInitialize();

	_relem[ElemDisp] = _shed->GetResource(QGWELM_TEXTLINE_DISP);
	_relem[ElemCaret] = _shed->GetResource(QGWELM_TEXTLINE_CARET);
	_relem[ElemSelect] = _shed->GetResource(QGWELM_TEXTLINE_SELECT);
	_relem[ElemInvert] = _shed->GetResource(QGWELM_TEXTLINE_INVERT);
}

//
void qwTextLine::OnChangeFocus(bool focus)
{
	qgStub* stub = qgRdh::Instance->GetStub();

	_isfocus = focus;

	if (focus)
	{
		ResetCaret();

		// IME 끔
		stub->SetImeEnable(false);
	}
	else
	{
		// IME 켬
		stub->SetImeEnable(true);
	}

	SetRefresh();
}

//
void qwTextLine::OnChangeFont(qgFont* font)
{
	kobj_unload(_scribe.scribe);

	// font는 반드시 NULL이 아니므로 그냥 써도 좋다
	_scribe.scribe = font->BuildScribe();
}

//
void qwTextLine::OnTextChanged(const char* str, kint len)
{
	qwCtrl::OnTextChanged(str, len);

	if (_bdisp.Width() != _bound.Width())
		_bdisp = _bound;

	if (_text_str.IsEmpty())
	{
		_vispos = 0;
		SetSelection(0, 0);
	}
	else
	{
		_vispos = 0;
		SetSelection(_text_str.Length(), -1);
	}
}

//
bool qwTextLine::OnUpdate(float adv)
{
	BuildBound();

	if (NeedRefresh())
	{
		if (_scribe.scribe)
		{
			qgScribeFont* scb = _scribe.scribe.Ptr();

			scb->Analyze(_text_info.size, _text_str.Data());
			scb->PointToX(_vispos, false, _scribe.first);
			scb->PointToX(_carpos, true, _scribe.right);
			scb->PointToX(_carpos, false, _scribe.caret);

			if (_carpos == _selpos)
			{
				_scribe.select = _scribe.caret;
				_scribe.range.Set(0, 0);
			}
			else
			{
				scb->PointToX(_selpos, false, _scribe.select);
				_scribe.range.Set(
					K_MIN(_scribe.caret, _scribe.select),
					K_MAX(_scribe.caret, _scribe.select));
			}
		}

		ResetRefresh();
	}

	//
	_bdisp = HasBorder() ? _client.Inflate(-_border.x, -_border.y) : _client;
	kint height = _bdisp.Height();
	kint textsize = _text_info.size;
	_text_info.anch.Set(0, height < textsize ? 0 : (height - textsize) / 2);

	//
	_caret.adv += adv;

	if (_caret.adv>_caret.blink)
	{
		_caret.adv = 0.0f;
		_caret.visible = !_caret.visible;
	}

	return true;
}

//
void qwTextLine::OnDraw()
{
	if (CanFill() && HasBorder())
		_shed->DrawFillRect(_client, qwCtrl::GetResource(), _tex);

	_shed->DrawFillRect(_bdisp, _relem[ElemDisp]);

	//
	if (HasText())
	{
		// 선택 배경
		if (_scribe.range.min != 0 || _scribe.range.max != 0)
		{
			tpl::Rect fll(_scribe.range.min, _bdisp.top, _scribe.range.max, _bdisp.bottom);
			fll.Intersect(_bdisp, fll.Offset(_bdisp.left - _scribe.first, 0));
			_shed->DrawFillRect(fll, _relem[ElemInvert]);
		}

		// 글자 출력, 유니코드 아님
		_shed->DrawAnchText(_font, _text_str.Data() + _vispos, _bdisp, _text_info, 0);

		// 선택 텍스트
		// ... 일단 패스
#if 0
		if (_scb_point.min != 0 || _scb_point.max != 0)
		{
			int n = HB_MAX(_visible_index, HB_MIN(_select_index, _caret_index));
			int c = HB_MAX(_select_index, _caret_index) - n;
			_shed->DrawCustomText(_font, rtslt, _select_color, _text__size, 0, _text_.ConstData() + n, _text__anch);
		}
#endif
	}

	// 캐럿
	if (_isfocus && _caret.visible)
	{
		tpl::Rect crt(
			_bdisp.left - _scribe.first + _scribe.caret - 1, _bdisp.top,
			_bdisp.left - _scribe.first + _scribe.caret + 1, _bdisp.bottom);

		if (!_insmode)
			crt.right = _bdisp.left - _scribe.first + _scribe.right + 1;

		_shed->DrawFillRect(crt, _relem[ElemCaret], _tex);
	}

	//
	qwCtrl::OnDraw();
}

//
const qbWidgetResource& qwTextLine::GetResource(qgWidgetElement nth)
{
	switch ((ksize_t)nth)
	{
		case QGWELM_TEXTLINE_DISP:
		case QGWELM_TEXTLINE_CARET:
		case QGWELM_TEXTLINE_SELECT:
		case QGWELM_TEXTLINE_INVERT:
			return _relem[nth - QGWELM_TEXTLINE_DISP];
	}

	return super_type::GetResource(nth);
}

//
bool qwTextLine::SetResource(qgWidgetElement nth, const qbWidgetResource& res)
{
	switch ((ksize_t)nth)
	{
		case QGWELM_TEXTLINE_DISP:
		case QGWELM_TEXTLINE_CARET:
		case QGWELM_TEXTLINE_SELECT:
		case QGWELM_TEXTLINE_INVERT:
			_relem[nth - QGWELM_TEXTLINE_DISP] = res;
			return true;
	}

	return super_type::SetResource(nth, res);
}

//
void qwTextLine::CopyText()
{
#if _SB_WINDOWS_
	if (_carpos != _selpos && OpenClipboard(NULL))
	{
		EmptyClipboard();

		int s = K_MIN(_carpos, _selpos);
		int e = K_MAX(_carpos, _selpos);
		int len = e - s;

		const char* psz = _text_str.Data() + s;
		ksize_t size = k_conv_utf8_to_utf16(NULL, 0, psz, len);

		HGLOBAL h = GlobalAlloc(GMEM_MOVEABLE, sizeof(kwchar)*(size + 1));

		if (h)
		{
			kwchar* pw = (kwchar*)GlobalLock(h);
			k_conv_utf8_to_utf16(pw, size + 1, psz, len);
			GlobalUnlock(h);

			SetClipboardData(CF_UNICODETEXT, h);
			GlobalFree(h);
		}

		CloseClipboard();
	}
#else
	// 헐...
#endif

	SetRefresh();
}

//
void qwTextLine::PasteText()
{
#if _SB_WINDOWS_
	ClearSelection();

	if (OpenClipboard(NULL))
	{
		HANDLE h = GetClipboardData(CF_UNICODETEXT);

		if (h)
		{
			kwchar* pw = (kwchar*)GlobalLock(h);

			if (pw)
			{
				ksize_t len = k_conv_utf16_to_utf8(NULL, 0, pw, 0);
				char* ptr = (char*)alloca(sizeof(char)*(len + 1));
				k_conv_utf16_to_utf8(ptr, len + 1, pw, 0);

				if (_text_str.Insert(_carpos, ptr))
				{
					// 이거 len이 좀 틀렸음 utf8 길이가 아님, 메모리 크기임
					// utf8len으로 하면 괜찮을까?
					len = k_utf8len(ptr);
					SetCaretPlace(_carpos + (kint)len);
				}

				_selpos = _carpos;

				GlobalUnlock(h);
			}
		}

		CloseClipboard();
	}
#else
	// 헐퀴...
#endif

	SetRefresh();
}

//
void qwTextLine::ResetCaret()
{
	_caret.visible = true;
	_caret.adv = 0.0f;

	SetRefresh();
}

//
void qwTextLine::SetCaretPlace(kint nth)
{
	// [2013-12-19 KIM] 
	// 여기 문자열 길이 관련 부분을 모두 UTF8 길이로 바꾸는게 낫지 않을까 생각함

	_carpos = nth;

	if (_scribe.scribe)
	{
		qgScribeFont* scb = _scribe.scribe.Ptr();

		scb->Analyze(_text_info.size, _text_str.Data());

		kint x1st, x, x2;
		scb->PointToX(_vispos, false, x1st);
		scb->PointToX(nth, false, x);

		if (nth == _text_str.Length())
			x2 = x;
		else
			scb->PointToX(nth, true, x2);

		if (x < x1st)
			_vispos = nth;
		else if (x2 > (x1st + _bdisp.Width()))
		{
			kint left = x2 - _bdisp.Width();

			kint n1st;
			bool trail;
			scb->XToPoint(left, n1st, trail);

			kint xn1st;
			scb->PointToX(n1st, false, xn1st);

			if (xn1st < left)
				++n1st;

			_vispos = n1st;
		}
	}

	SetRefresh();
}

//
void qwTextLine::SetSelection(kint start, kint end)
{
	SetCaretPlace(start);
	_selpos = end < 0 ? _text_str.Length() : end;
}

//
void qwTextLine::ClearSelection()
{
	int s = K_MIN(_carpos, _selpos);
	int e = K_MAX(_carpos, _selpos);

	SetCaretPlace(s);
	_selpos = _carpos;

	for (int i = s; i < e; i++)
		_text_str.RemoveAt(s);

	SetRefresh();
}

//
bool qwTextLine::OnMouseMove(kint x, kint y, const tpl::Point& relative, kushort state)
{
	if (_drag && _scribe.scribe)
	{
		kint x1st;
		_scribe.scribe->PointToX(_vispos, false, x1st);

		kint nth;
		bool trail;

		if (_scribe.scribe->XToPoint(x - _bdisp.left + x1st, nth, trail))
			SetCaretPlace(trail && nth < _text_str.Length() ? nth + 1 : nth);

		SetRefresh();

		return false;
	}

	return true;
}

//
bool qwTextLine::OnMouseDown(kint x, kint y, kushort button)
{
	if (_bdisp.OnPoint(x, y))
	{
		if (!_isfocus)
			_shed->SetFocus(this);

		_drag = true;

		if (_scribe.scribe)
		{
			kint x1st;
			_scribe.scribe->PointToX(_vispos, false, x1st);

			kint nth;
			bool trail;

			if (_scribe.scribe->XToPoint(x - _bdisp.left + x1st, nth, trail))
			{
				SetCaretPlace(trail && nth < _text_str.Length() ? nth + 1 : nth);
				_selpos = _carpos;
				ResetCaret();
			}
		}

		SetRefresh();

		return false;
	}

	return true;
}

//
bool qwTextLine::OnMouseUp(kint x, kint y, kushort button)
{
	_drag = false;

	SetRefresh();

	return true;
}

//
bool qwTextLine::OnMouseDouble(kint x, kint y, kushort button)
{
	return qwTextLine::OnMouseDown(x, y, button);
}

//
namespace _widget
{
	inline kint GetCaretLeft(const char* str, kint pos)
	{
		const char* psz = str + pos;
		const char* ppv = k_utf8_char_prev(psz);
		return pos - (kint)(psz - ppv);
	}

	inline kint GetCaretRight(const char* str, kint pos)
	{
		const char* psz = str + pos;
		const char* pnx = k_utf8_char_next(psz);
		return pos + (kint)(pnx - psz);
	}

	inline kint GetBreakLeft(const char* str, kint pos)
	{
		const char* psz = str + pos - 1;
		if (k_isspace(*psz) && pos > 1)
			--psz;
		for (; psz != str; --psz)
		{
			if (k_isspace(*psz))
				break;
		}
		return (kint)(psz - str);
	}

	inline kint GetBreakRight(const char* str, kint pos, kint len)
	{
		const char* psz = str + pos + 1;
		if (k_isspace(*psz) && pos < (len - 1))
			++psz;
		for (; *psz; ++psz)
		{
			if (k_isspace(*psz))
				break;
		}
		return (kint)(psz - str);
	}
}

//
bool qwTextLine::OnKeyDown(kbyte key, kbyte sub, kuint ch, bool repeat, kushort state)
{
	bool ret = false;

	switch (key)
	{
		case QIK_TAB:
			break;

		case QIK_HOME:		// 줄 앞으로
			SetCaretPlace(0);
			if (!K_OFLAG(state, QIS_SHIFT))
				_selpos = _carpos;
			ResetCaret();
			break;

		case QIK_END:		// 줄 뒤로
			SetCaretPlace(_text_str.Length());
			if (!K_OFLAG(state, QIS_SHIFT))
				_selpos = _carpos;
			ResetCaret();
			break;

		case QIK_INSERT:	// 껴넣기 모드 전환
			if (K_OFLAG(state, QIS_CTRL))
			{
				// 복사하기 CTRL+INS
				CopyText();
			}
			else if (K_OFLAG(state, QIS_SHIFT) && !ReadOnly())
			{
				// 붙여넣기 SHIFT+INS
				PasteText();

				if (_ecb_text_changed)
					_ecb_text_changed(this, _text_str.Data(), _text_str.Length());
			}
			else
			{
				_insmode = !_insmode;
				ret = true;
			}
			ResetCaret();
			break;

		case QIK_DELETE:	// 지우기
			if (!ReadOnly())
			{
				if (_carpos != _selpos)
				{
					ClearSelection();

					if (_ecb_text_cmd)
						_ecb_text_cmd(this, true);
				}
				else
				{
					kint end = _widget::GetCaretRight(_text_str.Data(), _carpos);

					if (_text_str.RemoveAt(_carpos, end - _carpos) && _ecb_text_changed)
						_ecb_text_changed(this, _text_str.Data(), _text_str.Length());
				}
			}
			ResetCaret();
			break;

		case QIK_LEFT:		// 왼쪽 글자로
			if (_carpos > 0)
			{
				if (!K_OFLAG(state, QIS_CTRL))
					SetCaretPlace(_widget::GetCaretLeft(_text_str.Data(), _carpos));
				else
					SetCaretPlace(_widget::GetBreakLeft(_text_str.Data(), _carpos));
			}
			if (!K_OFLAG(state, QIS_SHIFT))
			{
				// 영역 지정 SHIFT+LEFT
				_selpos = _carpos;
			}
			ResetCaret();
			break;

		case QIK_RIGHT:		// 오른쪽 글자로
			if (_carpos < _text_str.Length())
			{
				if (!K_OFLAG(state, QIS_CTRL))
					SetCaretPlace(_widget::GetCaretRight(_text_str.Data(), _carpos));
				else
					SetCaretPlace(_widget::GetBreakRight(_text_str.Data(), _carpos, _text_str.Length()));
			}
			if (!K_OFLAG(state, QIS_SHIFT))
			{
				// 영역 지정 SHIFT+RIGHT
				_selpos = _carpos;
			}
			ResetCaret();
			break;

		case QIK_UP:		// 윗 항목 = 윗 줄로
		case QIK_DOWN:		// 아래 항목 = 아랫 줄로
			break;

		case QIK_X:			// 잘라내기 CTRL+X
			if (ReadOnly() || !K_OFLAG(state, QIS_CTRL))
				ret = true;
			else
			{
				CopyText();
				ClearSelection();

				if (_ecb_text_changed)
					_ecb_text_changed(this, NULL, 0);
			}
			break;

		case QIK_C:			// 복사하기 CTRL+C
			if (!K_OFLAG(state, QIS_CTRL))
				ret = true;
			else
				CopyText();
			break;

		case QIK_V:			// 붙여넣기 CTRL+V
			if (ReadOnly() || !K_OFLAG(state, QIS_CTRL))
				ret = true;
			else
			{
				PasteText();

				if (_ecb_text_changed)
					_ecb_text_changed(this, _text_str.Data(), _text_str.Length());
			}
			break;

		case QIK_A:			// 전체 선택 CTRL+A
			if (!K_OFLAG(state, QIS_CTRL))
				ret = true;
			else
			{
				if (_selpos == _carpos)
				{
					_selpos = 0;
					SetCaretPlace(_text_str.Length());
				}
			}
			break;

		case QIK_ESCAPE:	// 편집 취소
			if (!ReadOnly() && _ecb_text_cmd)
				_ecb_text_cmd(this, false);

		default:
			ret = key == QIK_ESCAPE;
			break;
	}

	if (!ret)
		SetRefresh();

	return ret;
}

//
bool qwTextLine::OnChar(kint ch, bool repeat)
{
	if (!_isfocus)
		return true;

	switch (ch)
	{
		case 8:	// ASCII BACKSPACE
			if (!ReadOnly())
			{
				if (_carpos != _selpos)
				{
					ClearSelection();

					if (_ecb_text_changed)
						_ecb_text_changed(this, _text_str.Data(), _text_str.Length());
				}
				else if (_carpos > 0)
				{
					kint prev = _carpos;
					SetCaretPlace(_widget::GetCaretLeft(_text_str.Data(), _carpos));
					_selpos = _carpos;

					_text_str.RemoveAt(_carpos, prev - _carpos);

					if (_ecb_text_changed)
						_ecb_text_changed(this, _text_str.Data(), _text_str.Length());
				}
			}
			ResetCaret();
			break;

		case 13:	// ASCII CARRIGE RETURN
			if (!ReadOnly() && _ecb_text_cmd)
				_ecb_text_cmd(this, true);
			break;

#if _SB_WINDOWS_
		case 1:		//ctrl+a
		case 2:		//ctrl+b
		case 3:		//ctrl+c
		case 4:		//ctrl+d
		case 5:		//ctrl+e
		case 6:		//ctrl+f
		case 7:		//ctrl+g
		case 9:		//ctrl+i
		case 10:	//ctrl+j
		case 11:	//ctrl+k
		case 12:	//ctrl+l
		case 14:	//ctrl+n
		case 15:	//ctrl+o
		case 16:	//ctrl+p
		case 17:	//ctrl+q
		case 18:	//ctrl+r
		case 19:	//ctrl+s
		case 20:	//ctrl+t
		case 21:	//ctrl+u
		case 22:	//ctrl+v
		case 23:	//ctrl+w
		case 24:	//ctrl+x
		case 25:	//ctrl+y
		case 26:	//ctrl+z
		case 27:	//ctrl+[
		case 28:	//ctrl+backslash
		case 29:	//ctrl+]
			break;
#endif

		default:
			if (!ReadOnly())
			{
				if (_carpos != _selpos)
					ClearSelection();

				// 어... 윈도우에서 ch 유니코드 아님?
				// ASC 테이블은 UTF-16이랑 호환되니 패스

				if (ch >= 0x20 && ch < 0x7F)
				{
					if (_onlynum)
					{
						if (!((ch >= '0' && ch <= '9') || ch == '.' || ch == '+' || ch == '-'))
							break;
					}

					if (!_insmode && _carpos < (kint)_text_str.Length())
					{
						_text_str[_carpos] = (char)ch;
						SetCaretPlace(_carpos + 1);
						_selpos = _carpos;
					}
					else
					{
						if (_text_str.Insert(_carpos, (char)ch))
						{
							SetCaretPlace(_carpos + 1);
							_selpos = _carpos;
						}
					}

					if (_ecb_text_changed)
						_ecb_text_changed(this, _text_str.Data(), _text_str.Length());
				}
			}
			ResetCaret();
			break;
	}

	SetRefresh();

	return false;
}

#endif	// _QG_ENABLE_WIDGET
